package com.tradesolver;

import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

public class CsvService {

  Logger logger = Logger.getLogger(CsvService.class.getName());
  Dao dao = new Dao();
  TimeZone tz = TimeZone.getTimeZone("GMT+8:00");
  SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

  static long ONE_MONTH = 1000 * 60 * 60 * 24 * 30L;
  static long ONE_YEAR = 1000 * 60 * 60 * 24 * 365L;

  public TreeMap<String, TreeMap<String, Quote>> getCloseData(String[] tickers, String dateFrom) {
    TreeMap<String, TreeMap<String, Quote>> closeDataMap = new TreeMap<String, TreeMap<String, Quote>>();
    TreeMap<String, TreeMap<String, Quote>> quotesMap = getQuotes(tickers);

    for (String ticker : quotesMap.keySet()) {
      TreeMap<String, Quote> closeData = new TreeMap<String, Quote>();
      closeData.putAll(quotesMap.get(ticker).tailMap(dateFrom));
      closeDataMap.put(ticker, closeData);
    }

    return closeDataMap;
  }

  private static class TickerYears {
    String ticker;
    int years;
    ListedCompany company;
    Future<HTTPResponse> future;

    public TickerYears(String ticker, int years, ListedCompany company) {
      super();
      this.ticker = ticker;
      this.years = years;
      this.company = company;
    }
  }

  public boolean isCached(String ticker) {
    return dao.getListedCompany(ticker) != null;
  }

  public TreeMap<String, TreeMap<String, Quote>> getQuotes(String[] tickers) {

    TreeMap<String, TreeMap<String, Quote>> quotesMap = new TreeMap<String, TreeMap<String, Quote>>();

    List<String> validTickers = new ArrayList<String>();
    // filter known tickers
    for (String ticker : tickers) {
      if (Companies.getName(ticker) != null) {
        validTickers.add(ticker.toUpperCase().trim());
      }
    }

    
    for (String ticker : validTickers) {
      ListedCompany company = dao.getListedCompany(ticker);
      if (company != null) {
        quotesMap.put(ticker, retrieveInfo(company.quote));
      }
    }
    
//    // accumulate what's in the DB, accumulate what to url query
//    List<TickerYears> queryTickerYears = new ArrayList<TickerYears>();
//    for (String ticker : validTickers) {
//      ListedCompany company = dao.getListedCompany(ticker);
//      if (company == null) {
//        queryTickerYears.add(new TickerYears(ticker, 20, new ListedCompany(ticker))); // get
//                                                                                      // full
//      } else {
//        Calendar now = Calendar.getInstance(tz);
//        if (!company.maxDate.equals(sdf.format(now.getTime()))) {
//
//          Calendar lastUpdateStamp = Calendar.getInstance(tz);
//          lastUpdateStamp.setTime(company.updateDate);
//          int yearsToQuery = getYearsToQuery(company.maxDate, lastUpdateStamp);
//          if (yearsToQuery >= 0) {
//            queryTickerYears.add(new TickerYears(ticker, yearsToQuery, company)); // get
//                                                                                  // full
//          } else {
//            quotesMap.put(ticker, retrieveInfo(company.quote));
//          }
//
//        } else {
//          logger.info("Not updating as maxDate=today: " + company);
//          quotesMap.put(ticker, retrieveInfo(company.quote));
//        }
//      }
//    }
//
//    // merge
//    TreeMap<String, TreeMap<String, Quote>> freshQuotesMap = getCsv(queryTickerYears);
//    quotesMap.putAll(freshQuotesMap);

    return quotesMap;
  }

  private int getYearsToQuery(String maxDate, Calendar lastUpdateStamp) {
    int yearToQuery = -1;

    Calendar now = Calendar.getInstance(tz);

    // if updated today
    if (lastUpdateStamp.get(Calendar.YEAR) == now.get(Calendar.YEAR)
        && lastUpdateStamp.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR)) {
      // no multiple updates in an hour
      if (lastUpdateStamp.get(Calendar.HOUR_OF_DAY) < now.get(Calendar.HOUR_OF_DAY)) {
        if (now.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || now.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
          logger.info("Not updating as its a weekend and we've already updated today: " + lastUpdateStamp);
        } else { // weekday
          if (now.get(Calendar.HOUR_OF_DAY) < 12) {
            logger.info("Not updating as we've already updated today and trading day is not over: " + now);
          } else {
            yearToQuery = 0;
          }
        }
      } else {
        logger.info("Not updating as there was already an update this hour: " + lastUpdateStamp);
      }
    } else { // update today
      int dd = Integer.valueOf(maxDate.substring(6));
      int mm = Integer.valueOf(maxDate.substring(4, 6));
      int yyyy = Integer.valueOf(maxDate.substring(0, 4));

      long diff = now.getTimeInMillis() - new Date(yyyy, mm, dd).getTime();
      if (diff > ONE_MONTH) {
        yearToQuery = (int) (1 + diff / ONE_YEAR);
      } else {
        yearToQuery = 0;
      }
    }

    return yearToQuery;
  }

  private TreeMap<String, Quote> retrieveInfo(byte[] quote) {
    TreeMap<String, Quote> quotes;
    try {
      ByteArrayInputStream bis = new ByteArrayInputStream(quote);
      ObjectInputStream in;
      in = new ObjectInputStream(bis);
      quotes = (TreeMap) in.readObject();
      in.close();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    } catch (ClassNotFoundException e) {
      throw new IllegalStateException(e);
    }
    return quotes;
  }

  private void saveInfo(String ticker, ListedCompany company, TreeMap<String, Quote> quotes) {
    try {
      logger.info("Serializing map");
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(bos);
      out.writeObject(quotes);
      out.flush();
      out.close();
      logger.info("Done Serializing map");

      company.minDate = quotes.firstKey();
      company.maxDate = quotes.lastKey();
      company.updateDate = Calendar.getInstance(tz).getTime();
      company.quote = bos.toByteArray();

      if (company.insertDate == null) {
        company.insertDate = company.updateDate;
      }

      logger.info("Saving ListedCompany=" + company);
      dao.putListedCompany(company);
      logger.info("Done saving ListedCompany");

    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  private TreeMap<String, TreeMap<String, Quote>> getCsv(List<TickerYears> tickerYearsList) {
    TreeMap<String, TreeMap<String, Quote>> quotesMap = new TreeMap<String, TreeMap<String, Quote>>();

    URLFetchService service = URLFetchServiceFactory.getURLFetchService();

    for (TickerYears tickerYears : tickerYearsList) {
      String url = "http://222.127.103.50/servlet/%50%53%45%43%68%61%72%74%53%65%72%76%6C%65%74?securitySymbol="
          + tickerYears.ticker + "&years=" + tickerYears.years;
      logger.info("Fetching url=" + url);
      HTTPRequest req;
      try {
        req = new HTTPRequest(new URL(url), HTTPMethod.GET, FetchOptions.Builder.withDeadline(5));
      } catch (MalformedURLException e) {
        throw new IllegalStateException(e); // shouldn't happen
      }
      tickerYears.future = service.fetchAsync(req);
    }

    for (TickerYears tickerYears : tickerYearsList) {
      HTTPResponse res;

      TreeMap<String, Quote> existingQuotes = new TreeMap<String, Quote>();
      if (tickerYears.company.quote != null) {
        existingQuotes = retrieveInfo(tickerYears.company.quote);
      }

      try {
        res = tickerYears.future.get();
        byte[] content = res.getContent();
        String result = new String(content);
        TreeMap<String, Quote> quotes = parseResponse(result);
        quotes.putAll(existingQuotes); // add existing data
        saveInfo(tickerYears.ticker, tickerYears.company, quotes);

        quotesMap.put(tickerYears.ticker, quotes);

      } catch (InterruptedException e) {
        logger.severe(tickerYears.ticker + " " + e);

        Queue queue = QueueFactory.getDefaultQueue();
        queue.add(withUrl("/data").param("queueTicker", tickerYears.ticker));

        quotesMap.put(tickerYears.ticker, existingQuotes); // make do with whats
                                                           // available
      } catch (ExecutionException e) {
        logger.severe(tickerYears.ticker + " " + e);
        quotesMap.put(tickerYears.ticker, existingQuotes); // make do with whats
                                                           // available
      } catch (Exception e) {
        logger.severe(tickerYears.ticker + " " + e);
        quotesMap.put(tickerYears.ticker, existingQuotes); // make do with whats
                                                           // available
      }
    }

    return quotesMap;
  }

  private TreeMap<String, Quote> parseResponse(String response) {

    logger.info("Response=" + response);
    if (response.startsWith("Error")) {
      logger.severe("Returning empty");
      return new TreeMap<String, Quote>();
    }
    String[] parts = response.split(";");
    String[] opens = parts[0].split("\\|");
    String[] highs = parts[1].split("\\|");
    String[] lows = parts[2].split("\\|");
    String[] closes = parts[3].split("\\|");
    String[] volumes = parts[4].split("\\|");
    String[] dates = parts[5].split("\\|");

    logger.info("Constructing map");
    TreeMap<String, Quote> quotes = new TreeMap<String, Quote>();
    for (int i = 0; i < dates.length; i++) {
      quotes.put(dates[i], new Quote(dates[i], Float.valueOf(opens[i]), Float.valueOf(highs[i]),
          Float.valueOf(lows[i]), Float.valueOf(closes[i]), Float.valueOf(volumes[i])));
      // sb.append(dates[i]).append(",").append(closes[i]).append("\n");
    }
    logger.info("Done Constructing map");

    return quotes;
  }

  public void refreshTicker(String ticker, boolean overrideMaxDate) {
    int yearsToQuery = -1;
    ListedCompany company = dao.getListedCompany(ticker);
    if (company == null) {
      yearsToQuery = 20; // get full
    } else if (overrideMaxDate) {
      yearsToQuery = 2;
      logger.warning("Override maxDate, querying 2 years");
    } else {
      Calendar now = Calendar.getInstance(tz);
      if (!company.maxDate.equals(sdf.format(now.getTime()))) {
        Calendar lastUpdateStamp = Calendar.getInstance(tz);
        lastUpdateStamp.setTime(company.updateDate);
        yearsToQuery = getYearsToQuery(company.maxDate, lastUpdateStamp);
      }
    }

    if (yearsToQuery > -1) {
      TreeMap<String, Quote> quotes = new TreeMap<String, Quote>();
      if (company == null) {
        company = new ListedCompany(ticker);
      } else {
        if (company.quote != null) {
          quotes = retrieveInfo(company.quote);
        }
      }

      String url = "http://222.127.103.50/servlet/%50%53%45%43%68%61%72%74%53%65%72%76%6C%65%74?securitySymbol="
          + ticker + "&years=" + yearsToQuery;
      logger.info("Fetching url=" + url);
      URLFetchService service = URLFetchServiceFactory.getURLFetchService();
      try {
        HTTPRequest req = new HTTPRequest(new URL(url), HTTPMethod.GET, FetchOptions.Builder.withDeadline(20));
        Future<HTTPResponse> future = service.fetchAsync(req);
        HTTPResponse res = future.get();
        byte[] content = res.getContent();
        String result = new String(content);
        TreeMap<String, Quote> newQuotes = parseResponse(result);
        if (newQuotes.isEmpty()) {
          logger.info("Empty newQuotes, not saving");
        } else {
          quotes.putAll(newQuotes);
          if (quotes.isEmpty()) {
            logger.info("Empty quotes, not saving");
          } else {
            saveInfo(ticker, company, quotes);
          }
        }

      } catch (MalformedURLException e) {
        throw new IllegalStateException(e); // shouldn't happen
      } catch (InterruptedException e) {
        throw new IllegalStateException(e);
      } catch (ExecutionException e) {
        throw new IllegalStateException(e);
      }
    }

  }

  public void getAndStoreRealtime() {
    String url = "http://222.127.103.50/servlet/TickerServletFile";
    logger.info("Fetching url=" + url);
    URLFetchService service = URLFetchServiceFactory.getURLFetchService();
    try {
      HTTPRequest req = new HTTPRequest(new URL(url), HTTPMethod.GET, FetchOptions.Builder.withDeadline(20));
      Future<HTTPResponse> future = service.fetchAsync(req);
      HTTPResponse res = future.get();
      byte[] content = res.getContent();
      String result = new String(content);
      logger.info("Got data=" + result);

      String[] tokens = result.split(";");
      DateFormat sdf = new SimpleDateFormat("EEE MMM d, yyyy HH:mm:ss a");
      Date dateStamp = sdf.parse(tokens[3]);
      DateFormat yyyymmddhhmm = new SimpleDateFormat("yyyyMMddHHmm");
      String stamp = yyyymmddhhmm.format(dateStamp);

      logger.info("Saving stamp=" + stamp);

      StockUpdate su = new StockUpdate(stamp, result);
      dao.putStockUpdate(su);
      logger.info("done");

    } catch (MalformedURLException e) {
      logger.warning(e.getMessage());
    } catch (InterruptedException e) {
      logger.warning(e.getMessage());
    } catch (ExecutionException e) {
      logger.warning(e.getMessage());
    } catch (ParseException e) {
      logger.warning(e.getMessage());
    }
  }
}
